package qrcode;

import java.util.Iterator;

public class AlphaNumericModeSequence implements QRSequence {

  private Version version;

  private String text;

  private boolean allowLowercase;

  public AlphaNumericModeSequence(Version version, String text, boolean allowLowercase) {
    this.version = version;
    this.text = text;
    this.allowLowercase = allowLowercase;
  }

  @Override
  public ModeIndicator getModeIndicator() {
    return ModeIndicator.ALPHANUMERIC;
  }

  @Override
  public int getLengthInBits() {
    return 4 + getNumberOfBitsInCharacterCountIndicator() + 11 * (text.length() / 2) + 6 * (text.length() % 2);
  }

  @Override
  public Version getVersion() {
    return version;
  }

  @Override
  public boolean getSequenceBit(final int idx) {
    if (idx < 4) {
      return BitHelper.getBitAtStartingAtMostSignificant(getModeIndicator().asByte(), 4, idx);
    }
    int tidx = idx - 4;
    if (tidx < getNumberOfBitsInCharacterCountIndicator()) {
      return BitHelper.getBitAtStartingAtMostSignificant(text.length(), getNumberOfBitsInCharacterCountIndicator(),
          tidx);
    }
    tidx -= getNumberOfBitsInCharacterCountIndicator();
    final int doubleCharacterIdx = tidx / 11;
    final int numberOfDoubleChars = (text.length() + 1) / 2;
    final int bidx = tidx % 11;
    if (doubleCharacterIdx == numberOfDoubleChars - 1 && (text.length() % 2) == 1) {
      // last character of odd sequence
      final char ch = text.charAt(text.length() - 1);
      return BitHelper.getBitAtStartingAtMostSignificant(getCharacterValue(ch, allowLowercase), 6, bidx);
    }

    final char first = text.charAt(doubleCharacterIdx * 2);
    final char second = text.charAt(doubleCharacterIdx * 2 + 1);
    final int value1 = getCharacterValue(first, allowLowercase);
    final int value2 = getCharacterValue(second, allowLowercase);
    final int value = value1 * 45 + value2;
    return BitHelper.getBitAtStartingAtMostSignificant(value, 11, bidx);
  }

  public static int getCharacterValue(char ch, boolean allowLowercase) {
    if (Character.isLowerCase(ch) && allowLowercase)
      ch = Character.toUpperCase(ch);
    switch (ch) {
    case '0':
      return 0;
    case '1':
      return 1;
    case '2':
      return 2;
    case '3':
      return 3;
    case '4':
      return 4;
    case '5':
      return 5;
    case '6':
      return 6;
    case '7':
      return 7;
    case '8':
      return 8;
    case '9':
      return 9;
    case 'A':
      return 10;
    case 'B':
      return 11;
    case 'C':
      return 12;
    case 'D':
      return 13;
    case 'E':
      return 14;
    case 'F':
      return 15;
    case 'G':
      return 16;
    case 'H':
      return 17;
    case 'I':
      return 18;
    case 'J':
      return 19;
    case 'K':
      return 20;
    case 'L':
      return 21;
    case 'M':
      return 22;
    case 'N':
      return 23;
    case 'O':
      return 24;
    case 'P':
      return 25;
    case 'Q':
      return 26;
    case 'R':
      return 27;
    case 'S':
      return 28;
    case 'T':
      return 29;
    case 'U':
      return 30;
    case 'V':
      return 31;
    case 'W':
      return 32;
    case 'X':
      return 33;
    case 'Y':
      return 34;
    case 'Z':
      return 35;
    case ' ':
      return 36;
    case '$':
      return 37;
    case '%':
      return 38;
    case '*':
      return 39;
    case '+':
      return 40;
    case '-':
      return 41;
    case '.':
      return 42;
    case '/':
      return 43;
    case ':':
      return 44;
    default:
      throw new IllegalArgumentException("Invalid character in alphanumeric mode: (" + ((int) ch) + ") '" + ch + "'.");
    }
  }

  public int getNumberOfBitsInCharacterCountIndicator() {
    int versionNr = version.getVersionNumber().intValue();
    if (versionNr <= 9)
      return 9;
    if (versionNr <= 26)
      return 11;
    return 13;
  }

  @Override
  public Iterator<Boolean> iterator() {
    return new QRSequenceBitIterator(this);
  }

  /**
   * <pre>
   * 0010 000000101 10100100111 10100001001 101010
   * Mode length 4  1319        1289        42
   *                29,14       28, 29      42
   *                T  E         S  T       .
   * </pre>
   */
  public static void main(String[] args) {
    AlphaNumericModeSequence seq = new AlphaNumericModeSequence(Version.VERSION_1, "Test.", true);
    for (boolean b : seq) {
      System.out.print(b ? '1' : '0');
    }
  }
}